SPB Git forge

spb/qc26

Public
10commits 1branches 0releases
2.8 MBsize
maindefault branch
17 days agolast push
Python 51.6% TypeScript 46.7% CSS 1.7%
2.9 KB · 40 lines tsx
Raw Blame History
1import { ImageResponse } from "next/og";2import { ENGINE_URL } from "@/lib/api";3import { PARTIES } from "@/lib/parties";45export const runtime = "nodejs";6export const alt = "QC26 — Prévision par circonscription";7export const size = { width: 1200, height: 630 };8export const contentType = "image/png";910export default async function Image({ params }: { params: Promise<{ slug: string }> }) {11  const { slug } = await params;12  let d: { name: string; regionName: string | null; forecast?: { favorite: string; p: number; parties: { party: string; voteMean: number; pWin: number }[] } } | null = null;13  try { d = await fetch(`${ENGINE_URL}/api/ridings/${slug}`, { next: { revalidate: 300 } }).then((r) => (r.ok ? r.json() : null)); } catch { d = null; }14  const color = (id: string) => PARTIES.find((p) => p.id === id)?.color ?? "#999";15  const rows = (d?.forecast?.parties ?? []).filter((p) => p.party !== "aut").slice(0, 5);16  return new ImageResponse(17    (18      <div style={{ display: "flex", width: "100%", height: "100%", flexDirection: "column", background: "#F7F8FA", color: "#111318", padding: 56, fontFamily: "Inter, sans-serif" }}>19        <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between" }}>20          <div style={{ display: "flex", alignItems: "baseline", fontSize: 40, fontWeight: 800, letterSpacing: -2 }}><span>QC</span><span style={{ width: 4, height: 32, background: "#2563EB", margin: "0 6px", borderRadius: 2 }} /><span style={{ color: "#2563EB" }}>26</span></div>21          <div style={{ display: "flex", fontSize: 18, color: "#656B76", letterSpacing: 3, textTransform: "uppercase" }}>{d?.regionName ?? "Circonscription"}</div>22        </div>23        <div style={{ display: "flex", fontSize: 64, fontWeight: 800, letterSpacing: -2, marginTop: 24 }}>{d?.name ?? slug}</div>24        <div style={{ display: "flex", fontSize: 24, color: "#656B76", marginTop: 4 }}>Probabilité de victoire · projection QC26</div>25        <div style={{ display: "flex", flexDirection: "column", marginTop: 28, flex: 1 }}>26          {rows.map((p) => (27            <div key={p.party} style={{ display: "flex", alignItems: "center", gap: 18, marginBottom: 12 }}>28              <div style={{ display: "flex", width: 90, fontSize: 28, fontWeight: 700, color: color(p.party) }}>{p.party.toUpperCase()}</div>29              <div style={{ display: "flex", flex: 1, height: 24, background: "#E9ECF0", borderRadius: 6 }}><div style={{ display: "flex", width: `${p.pWin * 100}%`, height: 24, background: color(p.party), borderRadius: 6 }} /></div>30              <div style={{ display: "flex", width: 120, fontSize: 30, fontWeight: 800, textAlign: "right" }}>{Math.round(p.pWin * 100)} %</div>31            </div>32          ))}33        </div>34        <div style={{ display: "flex", justifyContent: "space-between", fontSize: 20, color: "#656B76" }}><span>Élection québécoise · 5 octobre 2026</span><span>qc26.ca</span></div>35      </div>36    ),37    { ...size },38  );39}40